package injections;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.google.common.collect.Iterators;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;

/**
 * Created by rehak on 2/11/14.
 */
public class JSONSource implements Source {

    private final DefinedInjection inj;

    public JSONSource(String json) throws IOException {
        JsonNode jsonNode = parseJSON(json);
        // root instance
        JsonNode rn = jsonNode.findValue("rootInstanceName");
        String rootInstanceName = null;
        if (rn != null) {
            rootInstanceName = rn.asText();
        }
        List<DefinedClass> defClasses = new ArrayList<>();
        // create replacements
        JsonNode repl = jsonNode.get("replacements");
        if (repl != null) {
            Iterator<JsonNode> it = repl.iterator();
            while (it.hasNext()) {
                JsonNode replacement = it.next();
                String toReplace = replacement.get("toReplace").asText();
                String replaceWith = replacement.get("replaceWith").asText();
            }
        }
        JsonNode classes = jsonNode.get("classes");
        // browse defined classes to inject
        if (classes != null) {
            Iterator<JsonNode> it = classes.iterator();
            while (it.hasNext()) {
                JsonNode cls = it.next();
                defClasses.add(parseClass(cls));
            }
        }
        this.inj = new DefinedInjection(rootInstanceName, defClasses);
    }

    private DefinedClass parseClass(JsonNode node) throws IOException {
        // class name (if set)
        JsonNode clnNode = node.get("className");
        String className;
        if (clnNode != null) {
            className = clnNode.asText();
        } else {
            className = null;
        }
        // instance name (if set)
        JsonNode inn = node.get("instanceName");
        String instanceName;
        if (inn == null) {
            // anonymous instance
            // instanceName = UUID.randomUUID().toString();
            instanceName = null;
        } else {
            instanceName = inn.asText();
        }
        // browse properties - perform replacement, anonymous in-line instance creation
        JsonNode props = node.get("properties");
        List<DefinedProperty> properties = parseProps(node, props);
        // constructor
        JsonNode construct = node.get("constructor");
        List<DefinedProperty> constrProps = parseAnonProps(node, construct);

        DefinedClass cls = new DefinedClass(instanceName, className, properties, constrProps);
        return cls;
    }

    private List<DefinedProperty> parseAnonProps(JsonNode node, JsonNode construct) throws IOException {
        List<DefinedProperty> properties = new ArrayList<>();
        if (construct != null && construct.isArray()) {
            ArrayNode arn = (ArrayNode) construct;
            Iterator<JsonNode> it = arn.iterator();
            int arg = 0;
            while (it.hasNext()) {
                JsonNode val = it.next();
                List<PropertyValue> propVals = toVals(val);
                DefinedProperty p = new DefinedProperty("arg" + arg, propVals);
                properties.add(p);
            }
        }
        return properties;
    }

    private List<PropertyValue> toVals(JsonNode value) throws IOException {
        List<PropertyValue> propVals = new ArrayList<>();
        // single value, or array?
        List<JsonNode> vals = new ArrayList<>();
        if (value.isArray()) {
            vals.addAll(Arrays.asList(Iterators.toArray(value.iterator(), JsonNode.class)));
        } else {
            vals.add(value);
        }
        // browse values
        for (JsonNode val : vals) {
            // raw value
            if (val.isNull()) {
                propVals.add(new PropertyValue(null));
            } else if (val.isTextual()) {
                String nodeVal = val.asText();
                propVals.add(new PropertyValue(nodeVal));
            } else if (val.isInt()) {
                propVals.add(new PropertyValue(val.asInt()));
            } else if (val.isFloat()) {
                propVals.add(new PropertyValue(val.asDouble()));
            } else if (val.isLong()) {
                propVals.add(new PropertyValue(val.asLong()));
            } else if (val.isBoolean()) {
                propVals.add(new PropertyValue(val.asBoolean()));
            } else if (val.isObject()) {
                DefinedClass defCls = parseClass(val);
                propVals.add(new PropertyValue(defCls));
            }
        }
        return propVals;
    }

    private List<DefinedProperty> parseProps(JsonNode parentNode, JsonNode props) throws IOException {
        List<DefinedProperty> properties = new ArrayList<>();
        if (props != null) {
            Iterator<String> propNames = props.fieldNames();
            while (propNames.hasNext()) {
                String propName = propNames.next();
                JsonNode value = props.get(propName);
                List<PropertyValue> propVals = toVals(value);
                DefinedProperty prop = new DefinedProperty(propName, propVals);
                properties.add(prop);
            }
        }
        return properties;
    }

    private JsonNode parseJSON(String jsonString) throws IOException {
        ObjectMapper mapper = new ObjectMapper();
        JsonNode rootNode = mapper.readValue(jsonString, JsonNode.class);
        return rootNode;
    }

    @Override
    public DefinedInjection get() {
        return inj;
    }

}
